Skip to content

Conversation

@sivakusayan
Copy link
Contributor

@sivakusayan sivakusayan commented Nov 19, 2025

Closes #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.

@sivakusayan sivakusayan force-pushed the fix/constant-fold-nexttoward branch from 2a6ad7f to ea98d6b Compare November 19, 2025 23:51
@sivakusayan sivakusayan marked this pull request as ready for review November 20, 2025 00:18
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms labels Nov 20, 2025
@sivakusayan
Copy link
Contributor Author

sivakusayan commented Nov 20, 2025

Note that this is a second try of this PR. I closed it because I realized I unnecessarily touched some APFloat code, and didn't want ADT folks to be pinged unnecessarily on future review.

Some notes:

  • I've read that constant folding long double has been disfavored before. I think it's okay here because the long double argument merely specifies the direction to travel, and I'm guessing the ticket wouldn't have been marked as "good first bug" if I had to do anything complicated here.
  • Because we're folding long double, the test cases aren't as thorough as they could be. The most thorough test cases are around nextafter, nextafterf, and the fp128 case of nexttoward and nexttowardf. I didn't know if it would be crossing the line into "too many tests" if I duplicated those tests to the other possible LLVM IR types for long double, so tests for x86_fp80 and ppc_fp128 are less thorough.
  • I'm not doing any special handling around SNaNs and QNaNs here. I didn't know if we desired any, the manpage doesn't make any distinctions. The LangRef also says that "Floating-point math operations are allowed to treat all NaNs as if they were quiet NaNs. For example, “pow(1.0, SNaN)” may be simplified to 1.0.".

@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-llvm-transforms

Author: Sayan Sivakumaran (sivakusayan)

Changes

Fixes issue #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.


Patch is 33.76 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168794.diff

6 Files Affected:

  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+68-1)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll (+226)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll (+243)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll (+113)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-x86-fp80.ll (+113)
  • (added) llvm/test/Transforms/InstCombine/floating-point-constants.ll (+48)
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index 4bece85d3cfbf..e0aaea2569334 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -2008,7 +2008,9 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
            Name == "log10f" || Name == "logb" || Name == "logbf" ||
            Name == "log1p" || Name == "log1pf";
   case 'n':
-    return Name == "nearbyint" || Name == "nearbyintf";
+    return Name == "nearbyint" || Name == "nearbyintf" || Name == "nextafter" ||
+           Name == "nextafterf" || Name == "nexttoward" ||
+           Name == "nexttowardf";
   case 'p':
     return Name == "pow" || Name == "powf";
   case 'r':
@@ -3174,6 +3176,53 @@ static Constant *evaluateCompare(const APFloat &Op1, const APFloat &Op2,
   return nullptr;
 }
 
+/// Returns the first NaN in the operand list if it exists, preserving the NaN
+/// payload if possible. Returns nullptr if no NaNs are in the list.
+static Constant *TryConstantFoldNaN(ArrayRef<APFloat> Operands,
+                                    const Type *RetTy) {
+  assert(RetTy != nullptr);
+  for (const APFloat &Op : Operands) {
+    if (Op.isNaN()) {
+      bool Unused;
+      APFloat Ret(Op);
+      Ret.convert(RetTy->getFltSemantics(), detail::rmNearestTiesToEven,
+                  &Unused);
+      return ConstantFP::get(RetTy->getContext(), Ret);
+    }
+  }
+  return nullptr;
+}
+
+static Constant *ConstantFoldNextToward(const APFloat &Op0, const APFloat &Op1,
+                                        const Type *RetTy,
+                                        bool *WouldSetErrno) {
+  assert(RetTy != nullptr);
+  *WouldSetErrno = false;
+
+  Constant *RetNaN = TryConstantFoldNaN({Op0, Op1}, RetTy);
+  if (RetNaN != nullptr) {
+    return RetNaN;
+  }
+
+  // Recall that the second argument of nexttoward is always a long double,
+  // so we may need to promote the first argument for comparisons to be valid.
+  bool LosesInfo;
+  APFloat PromotedOp0(Op0);
+  PromotedOp0.convert(Op1.getSemantics(), detail::rmNearestTiesToEven,
+                      &LosesInfo);
+  assert(!LosesInfo && "Unexpected lossy promotion");
+
+  if (PromotedOp0 == Op1)
+    return ConstantFP::get(RetTy->getContext(), Op0);
+
+  APFloat Next(Op0);
+  Next.next(/*nextDown=*/PromotedOp0 > Op1);
+  const bool DidOverflow = !Op0.isInfinity() && Next.isInfinity();
+  *WouldSetErrno = Next.isZero() || Next.isDenormal() || DidOverflow;
+
+  return ConstantFP::get(RetTy->getContext(), Next);
+}
+
 static Constant *ConstantFoldLibCall2(StringRef Name, Type *Ty,
                                       ArrayRef<Constant *> Operands,
                                       const TargetLibraryInfo *TLI) {
@@ -3233,6 +3282,14 @@ static Constant *ConstantFoldLibCall2(StringRef Name, Type *Ty,
     if (TLI->has(Func))
       return ConstantFoldBinaryFP(atan2, Op1V, Op2V, Ty);
     break;
+  case LibFunc_nextafter:
+  case LibFunc_nextafterf:
+  case LibFunc_nexttoward:
+  case LibFunc_nexttowardf:
+    if (TLI->has(Func)) {
+      bool Unused;
+      return ConstantFoldNextToward(Op1V, Op2V, Ty, &Unused);
+    }
   }
 
   return nullptr;
@@ -4685,6 +4742,16 @@ bool llvm::isMathLibCallNoop(const CallBase *Call,
         // may occur, so allow for that possibility.
         return !Op0.isZero() || !Op1.isZero();
 
+      case LibFunc_nextafter:
+      case LibFunc_nextafterf:
+      case LibFunc_nextafterl:
+      case LibFunc_nexttoward:
+      case LibFunc_nexttowardf:
+      case LibFunc_nexttowardl: {
+        bool WouldSetErrno;
+        ConstantFoldNextToward(Op0, Op1, F->getReturnType(), &WouldSetErrno);
+        return !WouldSetErrno;
+      }
       default:
         break;
       }
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
new file mode 100644
index 0000000000000..1650a71d872b5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
@@ -0,0 +1,226 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nextafter(double, double) #0
+declare float @nextafterf(float, float) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+define double @nextafter_up_direction() {
+; CHECK-LABEL: define double @nextafter_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %next = call double @nextafter(double 1.0, double 2.0)
+  ret double %next
+}
+
+define float @nextafterf_up_direction() {
+; CHECK-LABEL: define float @nextafterf_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %next = call float @nextafterf(float 1.0, float 2.0)
+  ret float %next
+}
+
+define double @nextafter_down_direction() {
+; CHECK-LABEL: define double @nextafter_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %next = call double @nextafter(double 1.0, double 0.0)
+  ret double %next
+}
+
+define float @nextafterf_down_direction() {
+; CHECK-LABEL: define float @nextafterf_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %next = call float @nextafterf(float 1.0, float 0.0)
+  ret float %next
+}
+
+define double @nextafter_equal_args() {
+; CHECK-LABEL: define double @nextafter_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %next = call double @nextafter(double 1.0, double 1.0)
+  ret double %next
+}
+
+define float @nextafterf_equal_args() {
+; CHECK-LABEL: define float @nextafterf_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %next = call float @nextafterf(float 1.0, float 1.0)
+  ret float %next
+}
+
+define double @nextafter_nan_with_payload() {
+; CHECK-LABEL: define double @nextafter_nan_with_payload() {
+; CHECK-NEXT:    ret double 0x7FF8000000000001
+;
+  %nan = load double, double* @dbl_nan
+  %tmp1 = bitcast double %nan to i64
+  %tmp2 = or i64 %tmp1, 1
+  %nan_with_payload = bitcast i64 %tmp2 to double
+  %next = call double @nextafter(double %nan_with_payload, double 1.0)
+  ret double %next
+
+}
+
+define float @nextafterf_nan_with_payload() {
+; CHECK-LABEL: define float @nextafterf_nan_with_payload() {
+; CHECK-NEXT:    ret float 0x7FF8000020000000
+;
+  %nan = load float, float* @flt_nan
+  %tmp1 = bitcast float %nan to i32
+  %tmp2 = or i32 %tmp1, 1
+  %nan_with_payload = bitcast i32 %tmp2 to float
+  %next = call float @nextafterf(float %nan_with_payload, float 1.0)
+  ret float %next
+}
+
+define double @nextafter_pos_overflow () {
+; CHECK-LABEL: define double @nextafter_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0x7FEFFFFFFFFFFFFF, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %arg1 = load double, double* @dbl_pos_max
+  %arg2 = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+
+define float @nextafterf_pos_overflow() {
+; CHECK-LABEL: define float @nextafterf_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x47EFFFFFE0000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %arg1 = load float, float* @flt_pos_max
+  %arg2 = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+
+define double @nextafter_neg_overflow() {
+; CHECK-LABEL: define double @nextafter_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0xFFEFFFFFFFFFFFFF, double 0xFFF0000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %arg1 = load double, double* @dbl_neg_max
+  %arg2 = load double, double* @dbl_neg_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+
+define float @nextafterf_neg_overflow() {
+; CHECK-LABEL: define float @nextafterf_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xC7EFFFFFE0000000, float 0xFFF0000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %arg1 = load float, float* @flt_neg_max
+  %arg2 = load float, float* @flt_neg_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+
+define double @nextafter_zero_from_above() {
+; CHECK-LABEL: define double @nextafter_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %arg = load double, double* @dbl_pos_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+
+define float @nextafterf_zero_from_above() {
+; CHECK-LABEL: define float @nextafterf_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %arg = load float, float* @flt_pos_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+
+define double @nextafter_zero_from_below() {
+; CHECK-LABEL: define double @nextafter_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double -4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %arg = load double, double* @dbl_neg_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+
+define float @nextafterf_zero_from_below() {
+; CHECK-LABEL: define float @nextafterf_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xB6A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %arg = load float, float* @flt_neg_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+
+define double @nextafter_subnormal() {
+; CHECK-LABEL: define double @nextafter_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %infinity = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %subnormal, double %infinity)
+  ret double %next
+}
+
+define float @nextafterf_subnormal() {
+; CHECK-LABEL: define float @nextafterf_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %infinity = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %subnormal, float %infinity)
+  ret float %next
+}
+
+define double @nextafter_poison() {
+; CHECK-LABEL: define double @nextafter_poison() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double poison, double 1.000000e+00)
+; CHECK-NEXT:    ret double [[NEXT]]
+;
+  %next = call double @nextafter(double poison, double 1.0)
+  ret double %next
+}
+
+define double @nextafterf_poison() {
+; CHECK-LABEL: define double @nextafterf_poison() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafterf(float poison, float 1.000000e+00)
+; CHECK-NEXT:    ret double [[NEXT]]
+;
+  %next = call double @nextafterf(float poison, float 1.0)
+  ret double %next
+}
+
+define double @nextafter_subnormal_readnone() {
+; CHECK-LABEL: define double @nextafter_subnormal_readnone() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0x7FF0000000000000) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %infinity = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %subnormal, double %infinity) readnone
+  ret double %next
+}
+
+define float @nextafterf_subnormal_readnone() {
+; CHECK-LABEL: define float @nextafterf_subnormal_readnone() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0x7FF0000000000000) #[[ATTR1]]
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %infinity = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %subnormal, float %infinity) readnone
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
new file mode 100644
index 0000000000000..577ea0c1b3fba
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
@@ -0,0 +1,243 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double, fp128) #0
+declare float @nexttowardf(float, fp128) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+define double @nexttoward_up_direction() {
+; CHECK-LABEL: define double @nexttoward_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %arg = fpext double 2.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+
+define float @nexttowardf_up_direction() {
+; CHECK-LABEL: define float @nexttowardf_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %arg = fpext float 2.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+
+define double @nexttoward_down_direction() {
+; CHECK-LABEL: define double @nexttoward_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %arg = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+
+define float @nexttowardf_down_direction() {
+; CHECK-LABEL: define float @nexttowardf_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %arg = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+
+define double @nexttoward_equal_args() {
+; CHECK-LABEL: define double @nexttoward_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %arg = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+
+define float @nexttowardf_equal_args() {
+; CHECK-LABEL: define float @nexttowardf_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %arg = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+
+define double @nexttoward_nan_with_payload() {
+; CHECK-LABEL: define double @nexttoward_nan_with_payload() {
+; CHECK-NEXT:    ret double 0x7FF8000000000001
+;
+  %nan = load double, double* @dbl_nan
+  %tmp1 = bitcast double %nan to i64
+  %tmp2 = or i64 %tmp1, 1
+  %nan_with_payload = bitcast i64 %tmp2 to double
+  %dummy_arg = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double %nan_with_payload, fp128 %dummy_arg)
+  ret double %next
+}
+
+define float @nexttowardf_nan_with_payload() {
+; CHECK-LABEL: define float @nexttowardf_nan_with_payload() {
+; CHECK-NEXT:    ret float 0x7FF8000020000000
+;
+  %nan = load float, float* @flt_nan
+  %tmp1 = bitcast float %nan to i32
+  %tmp2 = or i32 %tmp1, 1
+  %nan_with_payload = bitcast i32 %tmp2 to float
+  %dummy_arg = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float %nan_with_payload, fp128 %dummy_arg)
+  ret float %next
+}
+
+define double @nexttoward_pos_overflow() {
+; CHECK-LABEL: define double @nexttoward_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0x7FEFFFFFFFFFFFFF, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %pos_max = load double, double* @dbl_pos_max
+  %pos_inf = load double, double* @dbl_pos_infinity
+  %ext_pos_inf = fpext double %pos_inf to fp128
+  %next = call double @nexttoward(double %pos_max, fp128 %ext_pos_inf)
+  ret double %next
+}
+
+define float @nexttowardf_pos_overflow () {
+; CHECK-LABEL: define float @nexttowardf_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x47EFFFFFE0000000, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %pos_max = load float, float* @flt_pos_max
+  %pos_inf = load float, float* @flt_pos_infinity
+  %ext_pos_inf = fpext float %pos_inf to fp128
+  %next = call float @nexttowardf(float %pos_max, fp128 %ext_pos_inf)
+  ret float %next
+}
+
+define double @nexttoward_neg_overflow() {
+; CHECK-LABEL: define double @nexttoward_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0xFFEFFFFFFFFFFFFF, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %neg_max = load double, double* @dbl_neg_max
+  %neg_inf = load double, double* @dbl_neg_infinity
+  %ext_neg_inf = fpext double %neg_inf to fp128
+  %next = call double @nexttoward(double %neg_max, fp128 %ext_neg_inf)
+  ret double %next
+}
+
+define float @nexttowardf_neg_overflow() {
+; CHECK-LABEL: define float @nexttowardf_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xC7EFFFFFE0000000, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %neg_max = load float, float* @flt_neg_max
+  %neg_inf = load float, float* @flt_neg_infinity
+  %ext_neg_inf = fpext float %neg_inf to fp128
+  %next = call float @nexttowardf(float %neg_max, fp128 %ext_neg_inf)
+  ret float %next
+}
+
+define double @nexttoward_zero_from_above() {
+; CHECK-LABEL: define double @nexttoward_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+
+define float @nexttowardf_zero_from_above() {
+; CHECK-LABEL: define float @nexttowardf_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_pos_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+
+define double @nexttoward_zero_from_below() {
+; CHECK-LABEL: define double @nexttoward_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double -4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %subnormal = load double, double* @dbl_neg_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+
+define float @nexttowardf_zero_from_below() {
+; CHECK-LABEL: define float @nexttowardf_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xB6A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_neg_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+
+define double @nexttoward_subnormal() {
+; CHECK-LABEL: define double @nexttoward_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %target = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %target)
+  ret double %next
+}
+
+define float @nexttowardf_subnormal() {
+; CHECK-LABEL: define float @nexttowardf_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %target = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float...
[truncated]

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🐧 Linux x64 Test Results

  • 186557 tests passed
  • 4883 tests skipped

@zwuis
Copy link
Contributor

zwuis commented Nov 20, 2025

Fixes issue #74368.

You can use this format.

@zwuis zwuis requested a review from dtcxzyw November 20, 2025 16:41
; CHECK-LABEL: define double @nexttoward_nan_with_payload() {
; CHECK-NEXT: ret double 0x7FF8000000000001
;
%nan = load double, double* @dbl_nan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
%nan = load double, double* @dbl_nan
%nan = load double, ptr @dbl_nan

Use opaque pointer type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks. Also slightly simplified the nan_with_payload test since the intermediary bitcast is no longer needed.

@@ -0,0 +1,256 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
; RUN: opt -S -passes=instcombine < %s | FileCheck %s

There's no reason to use cat in tests

Copy link
Contributor Author

@sivakusayan sivakusayan Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it that way so I didn't have to copy-paste floating point constants everywhere, and could instead pull in the values from the floating-point-constants.ll file. I couldn't really find an established pattern for it, so I asked for suggestions in the LLVM Discord. Do you know of a better way to do this?

If it feels too strange, I could copy what other lit tests do. I see other tests like ilogb folding hardcode the constants for example.

declare double @nextafter(double, double) #0
declare float @nextafterf(float, float) #0

attributes #0 = { willreturn memory(errnomem: write) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@sivakusayan
Copy link
Contributor Author

I'm a bit confused why the Build CI Checks passed but the libcxx-ci check failed, even though my most recent commit just made minor modifications to a lit test. I think it's a networking error since we didn't even successfully set up the working directory:

# Creating "/home/libcxx-builder/.buildkite-agent/builds/google-libcxx-builder-android-a074c66145a4-1/llvm-project/libcxx-ci"
$ cd /home/libcxx-builder/.buildkite-agent/builds/google-libcxx-builder-android-a074c66145a4-1/llvm-project/libcxx-ci
$ git clone -v -- https://github.com/llvm/llvm-project.git .
Cloning into '.'...
POST git-upload-pack (175 bytes)
POST git-upload-pack (gzip 118352 to 58900 bytes)
remote: Enumerating objects: 6879442, done.
remote: Counting objects: 100% (1462/1462), done.
remote: Compressing objects: 100% (740/740), done.
Receiving objects:  86% (5916321/6879442), 2.60 GiB | 30.52 MiB/s

I did a full rebuild locally after clearing my cache and everything seems fine. Assuming I'm not misunderstanding, could someone restart the libcxx-ci job for me please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang can't optimize calls to nexttoward()

5 participants